<?php
namespace Swork\Db;

use Swork\Exception\DbException;

class ElasticSearchQuery
{
    /**
     * 支持的连接符
     */
    const CONNECTORS = [
        '$OR' => 1,
        '$AND' => 2,
        '$MIN_SCORE' => 3,
    ];

    /**
     * @var string
     */
    private $_tbl = null;

    /**
     * @var array
     */
    private $_cols = null;

    /**
     * @var array
     */
    private $_where = null;

    /**
     * @var int
     */
    private $_minScore = 0;

    /**
     * 字符储存容器
     * @var array
     */
    private $params = [];

    /**
     * 当前操作连接（match, term, range）
     * @var string
     */
    private $currentOP = null;

    /**
     * 编译后的SQL缓存
     * @var array
     */
    private static $cache = [];

    /**
     * MySqlQuery constructor.
     * @param string $tbl 表名（相当于Index）
     * @param array $cols 配置字段
     * @param array $where 数据条件
     */
    public function __construct(string $tbl, array $cols, array $where)
    {
        $this->_tbl = $tbl;
        $this->_cols = $cols;
        $this->_where = $where;
    }

    /**
     * 获取SQL数据条件
     * @return array
     * @throws
     */
    public function getCondition()
    {
        //先默认从缓存中获取
        $ckey = md5($this->_tbl . serialize($this->_where));
        if (isset(self::$cache[$ckey]))
        {
            return self::$cache[$ckey];
        }

        $where = [];

        //如果是采用原生查询条件
        if (isset($this->_where['$query']))
        {
            $where = $this->_where['$query'];
        }
        elseif (!empty($this->_where))
        {
            //解析条件
            $this->explain($this->_where);

            //合并SQL
            $count = count($this->params);
            if ($count == 1)
            {
                $where = $this->params[0];
            }
            elseif ($count > 1)
            {
                $where['bool']['must'] = $this->params;
            }
        }

        //合成数值
        $value = [
            'where' => $where,
            'min_score' => $this->_minScore,
        ];

        //写入缓存（当前线程有效，不是跨线程共享）
        self::$cache[$ckey] = $value;

        //返回
        return $value;
    }

    /**
     * 解析数据条件
     * @param array $where
     * @throws
     */
    private function explain(array $where)
    {
        foreach ($where as $key => $val)
        {
            $key = trim($key);
            $idx = strtoupper($key);
            $item = null;
            if (isset(self::CONNECTORS[$idx]))
            {
                switch ($idx)
                {
                    case '$OR':
                    case '$AND':
                    case '$MIN_SCORE':
                        $this->_minScore = $val;
                        break;
                }
                continue;
            }
            else
            {
                if (is_array($val))
                {
                    if (count($val) == 0)
                    {
                        throw new DbException("The column [{$this->_tbl}.$key] does not have value.", 3030);
                    }
                    foreach ($val as $op => $va)
                    {
                        $op = trim($op);
                        $op = strtoupper($op);
                        switch ($op)
                        {
                            case '>':
                                $item['gt'] = $va;
                                $this->currentOP = 'range';
                                break;
                            case '>=':
                                $item['gte'] = $va;
                                $this->currentOP = 'range';
                                break;
                            case '<':
                                $item['lt'] = $va;
                                $this->currentOP = 'range';
                                break;
                            case '<=':
                                $item['lte'] = $va;
                                $this->currentOP = 'range';
                                break;
                            case 'TERM':
                                $item = $va;
                                $this->currentOP = 'term';
                                break;
                            case 'MATCH':
                                $item = $va;
                                $this->currentOP = 'match';
                                break;
                            default:
                                throw new DbException("The column [{$this->_tbl}.$key] does not match operator.", 3031);
                        }
                    }
                }
                elseif ($key == '')
                {
                    $this->currentOP = 'query_string';
                    $key = 'query';
                    $item = $val;
                }
                else
                {
                    $item = $val;
                    $this->currentOP = 'match';
                }
            }

            //组装条件
            $this->params[] = [$this->currentOP => [$key => $item]];
        }
    }
}
